﻿using System;
using GeoAPI.Geometries;
using YJKGridTopologyCal.Algorithm;
using YJKGridTopologyCal.Geometries;
using YJKGridTopologyCal.Index.Strtree;
using YJKGridTopologyCal.Operation.Distance;

namespace YJKGridTopologyCal.Precision
{
	public class MinimumClearance
	{
		public static double GetDistance(IGeometry g)
		{
			return new MinimumClearance(g).GetDistance();
		}

		public static IGeometry GetLine(IGeometry g)
		{
			return new MinimumClearance(g).GetLine();
		}

		public MinimumClearance(IGeometry geom)
		{
			this._inputGeom = geom;
		}

		public double GetDistance()
		{
			this.Compute();
			return this._minClearance;
		}

		public ILineString GetLine()
		{
			this.Compute();
			if (this._minClearancePts == null || this._minClearancePts[0] == null)
			{
                return null;
				//return this._inputGeom.Factory.CreateLineString(null);
			}
			return this._inputGeom.Factory.CreateLineString(this._minClearancePts);
		}

		private void Compute()
		{
			if (this._minClearancePts != null)
			{
				return;
			}
			this._minClearancePts = new Coordinate[2];
			this._minClearance = double.MaxValue;
			if (this._inputGeom.IsEmpty)
			{
				return;
			}
			FacetSequence[] array = FacetSequenceTreeBuilder.BuildSTRtree(this._inputGeom).NearestNeighbour(new MinimumClearance.MinClearanceDistance());
			MinimumClearance.MinClearanceDistance minClearanceDistance = new MinimumClearance.MinClearanceDistance();
			this._minClearance = minClearanceDistance.Distance(array[0], array[1]);
			this._minClearancePts = minClearanceDistance.Coordinates;
		}

		private readonly IGeometry _inputGeom;

		private double _minClearance;

		private Coordinate[] _minClearancePts;

		private class MinClearanceDistance : IItemDistance<Envelope, FacetSequence>
		{
			public Coordinate[] Coordinates
			{
				get
				{
					return this._minPts;
				}
			}

			public double Distance(IBoundable<Envelope, FacetSequence> b1, IBoundable<Envelope, FacetSequence> b2)
			{
				FacetSequence item = b1.Item;
				FacetSequence item2 = b2.Item;
				this._minDist = double.MaxValue;
				return this.Distance(item, item2);
			}

			public double Distance(FacetSequence fs1, FacetSequence fs2)
			{
				this.VertexDistance(fs1, fs2);
				if (fs1.Count == 1 && fs2.Count == 1)
				{
					return this._minDist;
				}
				if (this._minDist <= 0.0)
				{
					return this._minDist;
				}
				this.SegmentDistance(fs1, fs2);
				if (this._minDist <= 0.0)
				{
					return this._minDist;
				}
				this.SegmentDistance(fs2, fs1);
				return this._minDist;
			}

			private double VertexDistance(FacetSequence fs1, FacetSequence fs2)
			{
				for (int i = 0; i < fs1.Count; i++)
				{
					for (int j = 0; j < fs2.Count; j++)
					{
						Coordinate coordinate = fs1.GetCoordinate(i);
						Coordinate coordinate2 = fs2.GetCoordinate(j);
						if (!coordinate.Equals2D(coordinate2))
						{
							double num = coordinate.Distance(coordinate2);
							if (num < this._minDist)
							{
								this._minDist = num;
								this._minPts[0] = coordinate;
								this._minPts[1] = coordinate2;
								if (num == 0.0)
								{
									return num;
								}
							}
						}
					}
				}
				return this._minDist;
			}

			private double SegmentDistance(FacetSequence fs1, FacetSequence fs2)
			{
				for (int i = 0; i < fs1.Count; i++)
				{
					for (int j = 1; j < fs2.Count; j++)
					{
						Coordinate coordinate = fs1.GetCoordinate(i);
						Coordinate coordinate2 = fs2.GetCoordinate(j - 1);
						Coordinate coordinate3 = fs2.GetCoordinate(j);
						if (!coordinate.Equals2D(coordinate2) && !coordinate.Equals2D(coordinate3))
						{
							double num = CGAlgorithms.DistancePointLine(coordinate, coordinate2, coordinate3);
							if (num < this._minDist)
							{
								this._minDist = num;
								this.UpdatePts(coordinate, coordinate2, coordinate3);
								if (num == 0.0)
								{
									return num;
								}
							}
						}
					}
				}
				return this._minDist;
			}

			private void UpdatePts(Coordinate p, Coordinate seg0, Coordinate seg1)
			{
				this._minPts[0] = p;
				LineSegment lineSegment = new LineSegment(seg0, seg1);
				this._minPts[1] = new Coordinate(lineSegment.ClosestPoint(p));
			}

			private double _minDist = double.MaxValue;

			private readonly Coordinate[] _minPts = new Coordinate[2];
		}
	}
}
